﻿using Abp.AutoMapper;
using Abp.Dependency;
using Abp.Specifications;
using Abp.Timing;
using DotNetCore.CAP;
using Hicap.QRCodeWarehouse.Core.Common;
using Hicap.QRCodeWarehouse.Core.EFCore;
using Hicap.QRCodeWarehouse.Core.Models.DbSet;
using Hicap.QRCodeWarehouse.Core.Models.Other;
using Hicap.QRCodeWarehouse.Core.Models.Transfer;
using Hicap.QRCodeWarehouse.Core.Notify;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Hicap.QRCodeWarehouse.Core.Managers
{
    public class MonitorManager : IMonitorManager
    {
        private readonly IGenerationTaskManager _taskManager;
        private readonly IProductManager _productManager;
        private readonly IGenerationRuleManager _ruleManager;
        private readonly IFileRepositoryManager _fileManager;
        private readonly ICapPublisher _capBus;
        private readonly NotifyHub _notifyHub;

        private readonly IUnitOfWork _unitOfWork;
        private readonly IRepository<MonitorHistory> _repository;

        public MonitorManager(IGenerationTaskManager taskManager, IProductManager productManager, IGenerationRuleManager ruleManager, ICapPublisher capBus, NotifyHub notifyHub, IFileRepositoryManager fileManager, IUnitOfWork unitOfWork)
        {
            _taskManager = taskManager;
            _productManager = productManager;
            _ruleManager = ruleManager;
            _capBus = capBus;
            _notifyHub = notifyHub;
            _fileManager = fileManager;
            _unitOfWork = unitOfWork;
            _repository = unitOfWork.GetRepository<MonitorHistory>();
        }

        public IQueryable<MonitorHistory> GetMonitorInfos(Tuple<DateTime?, DateTime?> timeRange, DateTime? recordTime)
        {
            Expression<Func<MonitorHistory, bool>> expression = x => x.RecordTime > DateTime.MinValue;

            if (recordTime != null)
                expression = expression.And(x => x.RecordTime == recordTime);
            else
            {
                if (timeRange.Item1 != null)
                    expression = expression.And(x => x.RecordTime >= timeRange.Item1.Value);

                if (timeRange.Item2 != null)
                    expression = expression.And(x => x.RecordTime <= timeRange.Item2.Value);
            }

            return _repository.GetQuery().Where(expression);
        }

        public async Task GetMonitorInfos(int type)
        {
            await _capBus.PublishAsync<int>(MessageQueueChanelConst.MonitorServiceAction, type, MessageQueueChanelConst.MonitorServiceAction + ".End");
        }

        [CapSubscribe(MessageQueueChanelConst.MonitorServiceAction + ".End", Group = SysConsts.SysName)]
        public void FinalFeedBackMessage(ActionResult result)
        {
            Task.Run(async () =>
            {
                await _notifyHub.SendToGroup(NotificationMethod.MonitorFeedBack + ".End", JsonConvert.SerializeObject(result));
            });

            if (result.IsSuccessed)
                Log.Information($"FeedBack Finished:{MessageQueueChanelConst.MonitorServiceAction}--{result}");
            else
                Log.Error($"FeedBack have a Error:{MessageQueueChanelConst.MonitorServiceAction}--{result}");
        }

        [CapSubscribe(MessageQueueChanelConst.MonitorServiceFeedBack, Group = SysConsts.SysName)]
        public async Task<ActionResult> DispatchMonitorFeedback(MonitorFeedback[] data)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            try
            {
                MonitorInfosFeedback monitor =
                    new MonitorInfosFeedback
                    {
                        InnerRealTimeInfos = data.Select(x => x.InnerRealTime).ToList(),
                        OutterRealTimeInfos = data.Select(x => x.OutterRealTime).ToList()
                    };

                using (IDisposableDependencyObjectWrapper<IUnitOfWork> resolveObject =
                    IocManager.Instance.ResolveAsDisposable<IUnitOfWork>())
                {
                    using (IUnitOfWork uow = resolveObject.Object)
                    {
                        await CorrectionProductInfos(monitor, uow);

                        await GetCurrentMonitorInfos(data, monitor);

                        string monitorString = JsonConvert.SerializeObject(monitor);
                        ;
                        await _notifyHub.SendToGroup(NotificationMethod.MonitorFeedBack, monitorString);

                        if (data.Length > 0 && data.FirstOrDefault()?.MonitorActionType == 0)
                        {
                            await uow.SaveChangesAsync();

                            return new ActionResult(MessageQueueChanelConst.MonitorServiceFeedBack, true, watch.ElapsedMilliseconds,
                                Clock.Now, data);
                        }

                        await InsertMonitorHistory(uow, monitorString);

                        await uow.SaveChangesAsync();
                    }
                }

                return new ActionResult(MessageQueueChanelConst.MonitorServiceFeedBack, true, watch.ElapsedMilliseconds,
                    Clock.Now, data);
            }
            catch (Exception e)
            {
                return new ActionResult(MessageQueueChanelConst.MonitorServiceFeedBack, false, watch.ElapsedMilliseconds,
                    Clock.Now, data, e);
            }
        }

        private static async Task InsertMonitorHistory(IUnitOfWork uow, string monitorString)
        {
            await uow.GetRepository<MonitorHistory>().InsertAsync(new MonitorHistory()
            {
                Infos = monitorString,
                IsDeleted = false,
                RecordTime = Clock.Now
            });
        }

        private async Task GetCurrentMonitorInfos(MonitorFeedback[] data, MonitorInfosFeedback monitor)
        {
            List<Task<List<dynamic>>> tasks = new List<Task<List<dynamic>>>()
                        {
                            _taskManager.GetAll(),
                            _ruleManager.GetAll(),
                            _fileManager.GetAll(),
                        };

            List<dynamic>[] queryResult = await Task.WhenAll(tasks);

            monitor.TasksInfo = queryResult[0].GroupBy(x => x.SubordinateCompanyId).Select(
                s => new
                {
                    CompanyId = s.Key,
                    ManualInnerCount = s.Count(item =>
                        item.Type == GenerationTaskType.Manual && item.CodeType == GenerationRuleType.Inner),
                    ManualOutterCount = s.Count(item =>
                        item.Type == GenerationTaskType.Manual && item.CodeType == GenerationRuleType.Outer),
                    AutoInnerCount = s.Count(item =>
                        item.Type == GenerationTaskType.Timed && item.CodeType == GenerationRuleType.Inner),
                    AutoOutterCount = s.Count(item =>
                        item.Type == GenerationTaskType.Timed && item.CodeType == GenerationRuleType.Outer),
                });
            monitor.RulesInfo = queryResult[1].GroupBy(x => x.SubordinateCompanyId).Select(
                s => new
                {
                    CompanyId = s.Key,
                    InnerCount = s.Count(item => item.GenerationRuleType == GenerationRuleType.Inner),
                    OutterCount = s.Count(item => item.GenerationRuleType == GenerationRuleType.Outer)
                });
            monitor.FilesInfo = queryResult[2].GroupBy(x => x.UserId).Select(
                s => new
                {
                    UserId = s.Key,
                    UploadCount = s.Count(item => item.FileSourceType == FileSourceTypeEnum.Upload),
                    DownloadCount = s.Count(item => item.FileSourceType == FileSourceTypeEnum.Download),
                    DowloadTimes = s.Sum(item => item.DownloadTimes),
                });

            monitor.WarehouseInfos = FillInAuditLog(data, queryResult[0]);
        }

        private async Task CorrectionProductInfos(MonitorInfosFeedback monitor, IUnitOfWork uow)
        {
            IRepository<Product> repository = uow.GetRepository<Product>();
            foreach (RealTimeWarehouseMonitor innerData in monitor.InnerRealTimeInfos)
            {
                Product entity = repository.FirstOrDefault(innerData.ProductId);
                bool flag = false;

                if (entity.TotoalInnerCount != innerData.TotalCount)
                {
                    entity.TotoalInnerCount = innerData.TotalCount;
                    flag = true;
                }

                if (entity.TotoalDownloadInnerCount != innerData.DownloadedCount)
                {
                    entity.TotoalDownloadInnerCount = innerData.DownloadedCount;
                    flag = true;
                }

                if (entity.TotoalUsedInnerCount != innerData.UsedCount)
                {
                    entity.TotoalUsedInnerCount = innerData.UsedCount;
                    flag = true;
                }

                RealTimeWarehouseMonitor outterData =
                    monitor.OutterRealTimeInfos.FirstOrDefault(x => x.ProductId == innerData.ProductId);

                if (outterData != null)
                {
                    if (entity.TotoalOuterCount != outterData.TotalCount)
                    {
                        entity.TotoalOuterCount = outterData.TotalCount;
                        flag = true;
                    }
                    if (entity.TotoalDownloadOuterCount != outterData.DownloadedCount)
                    {
                        entity.TotoalDownloadOuterCount = outterData.DownloadedCount;
                        flag = true;
                    }
                    if (entity.TotoalUsedOuterCount != outterData.UsedCount)
                    {
                        entity.TotoalUsedOuterCount = outterData.UsedCount;
                        flag = true;
                    }
                }

                if (flag)
                    await repository.UpdateAsync(entity);

                monitor.ProductsInfo.Add(entity);
            }
        }

        private List<WarhouseMonitorFeedback> FillInAuditLog(MonitorFeedback[] data, List<dynamic> result)
        {
            List<WarhouseMonitorFeedback> warehouseFeedbackList = new List<WarhouseMonitorFeedback>();

            data.ToList().ForEach(x =>
            {
                WarhouseMonitorFeedback item = x.Overview.MapTo<WarhouseMonitorFeedback>();

                item.AuditLog.ForEach(y =>
                {
                    y.TaskDetail = result.FirstOrDefault(z => z.Id == y.TaskId);
                });

                warehouseFeedbackList.Add(item);
            });

            return warehouseFeedbackList;
        }
    }
}